import dataclasses as dcl
import itertools
import os
from abc import ABC, abstractmethod
from storage_utils.storage_utils import StorageAbstractLayer
import pickle
import base64

class CfgContainer(dict):
    def __init__(self, root):
        if not isinstance(root, dict):
            return

        for (k, v) in root.items():
            val = (CfgContainer(v) if isinstance(v, dict) else v)
            setattr(self, k, val)
            self[k] = val

    def select(self, path):
        path = (path if isinstance(path, list) else path.split('.'))

        val = self
        for p in path:
            val = val.get(p, dcl.MISSING)
            if val is dcl.MISSING:
                break
        return val

    def set(self, path, value):
        path = (path if isinstance(path, list) else path.split('.'))

        obj = self.select(path[:-1])
        obj[path[-1]] = value
        setattr(obj, path[-1], value)

    def to_dict(self):
        return {
            k: (v.to_dict() if isinstance(v, CfgContainer) else v)
            for (k, v) in self.items()
        }

def iterate_load_paths(search_paths, load_paths):
    for spath in search_paths:
        for lpath in load_paths:
            yield os.path.join(spath, lpath)

class GenericLoader(ABC):
    def __init__(self, bootstrap_cfg):
        self.bootstrap_cfg = bootstrap_cfg

    @abstractmethod
    def load(self):
        return []

class LocalFilesystemLoader(GenericLoader):
    @abstractmethod
    def get_load_paths(self):
        return None

    @abstractmethod
    def proc_file_data(self, file_handle):
        return None

    @abstractmethod
    def get_file_type(self):
        return ''

    def load(self):
        all_files = []
        try:
            for spath in iterate_load_paths(
                            self.bootstrap_cfg.common.local.search_paths,
                            self.get_load_paths()):
                if os.path.isfile(spath):
                    all_files.append(spath)
                elif os.path.isdir(spath):
                    all_files.extend(self._get_dir_files(spath))
                else:
                    continue

            if len(all_files) < 1:
                raise Exception('No files found')

        except Exception as e:
            raise Exception(f'Error finding {self.get_file_type()} files: '
                            f'{str(e)}') from None

        ret = []
        for file in all_files:
            try:
                with open(file,encoding='utf-8') as fin:
                    obj = self.proc_file_data(fin)
                ret.append({'id': file, 'obj': obj})
            except Exception as e:
                print(f'Error loading {self.get_file_type()} "{file}" '
                      f'(skipped): {str(e)}')
        return ret

    def _get_dir_files(self, dir):
        ret = []
        for (dpath, _, files) in os.walk(dir):
            for file in sorted(files):
                ret.append(os.path.join(dpath, file))

        return ret



class ElasticserchLoader(GenericLoader):

    def load(self):
        dev_query = {"size": 1000,"query": {"terms": {"file_type": ["2", "3"]}}}
        user_query = {"size": 1000,"query": {"term": {"file_type": {"value": "1"}}}}
        dev_config = self.connect(dev_query)
        user_config = self.connect(user_query)
        config  = list(itertools.chain(dev_config,user_config))
        return config


    def parse_config(self):
        pass


    def connect(self,query):
        print(self.bootstrap_cfg)
        hosts = self.bootstrap_cfg.config.es.hosts
        user = self.bootstrap_cfg.config.es.user
        password = self.bootstrap_cfg.config.es.password
        index = self.bootstrap_cfg.config.es.index
        config = {'storage_utils': {'platform_make': 1,'es': {'user': user, 'password': password,'hosts': hosts}}}
        ret = []
        try:
            stoage_obj = StorageAbstractLayer(config=config)
            stoage_obj.es_obj.init_es()
            es_rest = stoage_obj.es_obj.es_cli.search(index=index, body=query)['hits']['hits']
            for item in es_rest:
                config = pickle.loads(base64.b64decode(item['_source']['bin']))
                ret.append({'id': item['_source']['ad_id'] if 'ad_id' in item['_source'] else item['_id'], 'obj': config})
        except Exception as e:
            print(f'connect elasticsearch error:{e}')
        return ret


class OSSLoader(GenericLoader):

    def load(self):
        table = self.bootstrap_cfg.config.oss.table
        dev_query = f"select * from {table} where file_type = '2' or file_type = '3'"
        user_query =  f"select * from {table} where file_type = '1'"
        dev_config = self.connect(dev_query)
        user_config = self.connect(user_query)
        config  = list(itertools.chain(dev_config,user_config))
        return config


    def parse_config(self):
        pass


    def connect(self,query):
        access_id = self.bootstrap_cfg.config.oss.access_id
        access_secret = self.bootstrap_cfg.config.oss.access_secret
        endpoint = self.bootstrap_cfg.config.oss.endpoint
        bucket_name = self.bootstrap_cfg.config.oss.bucket_name
        scheme = self.bootstrap_cfg.config.oss.scheme
        db = self.bootstrap_cfg.config.oss.db
        table = self.bootstrap_cfg.config.oss.table
        stoage_config = {'storage_utils': {'platform_make': 2, 'oss': {'access_id': access_id,
                                                                       'access_secret': access_secret,
                                                                       'endpoint': endpoint,
                                                                       'bucket_name': bucket_name,
                                                                       'scheme':scheme}}}
        ret = []
        try:
            stoage_obj = StorageAbstractLayer(config=stoage_config)
            res = stoage_obj.Read(db, table, query, 2)
            for index, row in res.iterrows():
                config = pickle.loads(base64.b64decode(row['bin']))
                ret.append({'id': row['_id'] if '_id' in row else row['ad_id'], 'obj': config})
        except Exception as e:
            print(f'connect oss error: {e}')
        return ret


class HiveLoader(GenericLoader):

    def load(self):
        table = self.bootstrap_cfg.config.hive.table
        dev_query = f"select * from {table} where file_type = '2' or file_type = '3'"
        user_query =  f"select * from {table} where file_type = '1'"

        dev_config = self.connect(dev_query)
        user_config = self.connect(user_query)
        config  = list(itertools.chain(dev_config,user_config))
        return config


    def parse_config(self):
        pass


    def connect(self,query):
        host = self.bootstrap_cfg.config.hive.host
        port = self.bootstrap_cfg.config.hive.port
        db = self.bootstrap_cfg.config.hive.db
        table = self.bootstrap_cfg.config.hive.table
        username = self.bootstrap_cfg.config.hive.username
        password = self.bootstrap_cfg.config.hive.password
        auth = self.bootstrap_cfg.config.hive.auth
        kerberos_conf = self.bootstrap_cfg.config.hive.kerberos_conf

        storage_config =  {'storage_utils':{
                'platform_make': 'hive',
                'hive':{
                    'host': host,
                    'port': port,
                    'username': username,
                    'password': password,
                    'auth': auth,
                    'kerberos_conf': kerberos_conf
                }}}
        ret = []
        try:
            stoage_obj = StorageAbstractLayer(config=storage_config)
            res = stoage_obj.Read(db, table, query, None)
            for index, row in res.iterrows():
                config = pickle.loads(base64.b64decode(row['bin']))
                ret.append({'id': row['_id'] if '_id' in row else row['ad_id'], 'obj': config})
        except Exception as e:
            print(f'connect hive error: {e}')
        return ret

def iterate_loaders(loader_names, loader_map, args):
    return list(itertools.chain.from_iterable([
        (loader_map[lname](*args)).load() for lname in loader_names
    ]))

